package cn.isjinhao.crws.sql.grammar;

import cn.isjinhao.crws.sql.grammar.AssignmentList.Entry;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.SingleSelect;
import cn.isjinhao.crws.sql.lexical.scan.TokenSequence;
import cn.isjinhao.crws.sql.lexical.token.Token;
import cn.isjinhao.crws.sql.lexical.token.TokenType;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * logic            ->  condition (("or" | "and") condition)*
 * condition        ->  comparison
 * comparison       ->  binaryArithmetic ((">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains") binaryArithmetic)?
 * binaryArithmetic ->  unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
 * unaryArithmetic  ->  ("!"|"-") unaryArithmetic | primary
 *
 * primary          ->  "true" | "false" | "null" | NUMBER | STRING
 * function         ->  functionName "(" binaryArithmetic? ("," binaryArithmetic)* ")"
 * functionName     ->  "date_to_timestamp" | "date_to_str" | "str_to_date" | "str_to_timestamp" | "timestamp_to_date" | "timestamp_to_str" | "now"
 *
 * entryList        ->  (IDENTIFIER "=" binaryArithmetic) ("," IDENTIFIER "=" binaryArithmetic)*
 *
 * <br/>
 *
 * AbstractParser需要满足一个特点：tokenSequence用于指向下一次即将处理的token的位置。
 *
 * @Author ISJINHAO
 * @Date 2021/4/5 12:11
 */
public abstract class AbstractParser<T> implements Parser<T> {

    protected final TokenSequence tokenSequence;
    protected AbstractParser(TokenSequence tokenSequence) {
        this.tokenSequence = tokenSequence;
    }


    /**
     * condition (("or" | "and") condition)*
     */
    protected CurdStatement logic() {
        CurdStatement left = condition();
        while(tokenSequence.curEqual(TokenType.OR, TokenType.AND)) {
            Token token = tokenSequence.takeCur();
            tokenSequence.advance();
            CurdStatement right = condition();
            left = new Logic(left, token, right);
        }
        return left;
    }


    /**
     * comparison
     */
    protected CurdStatement condition() {
        return comparison();
    }


    /**
     * binaryArithmetic ((">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains") binaryArithmetic)?
     */
    protected CurdStatement comparison() {
        CurdStatement left = binaryArithmetic();
        Token token = comparisonSymbol();
        if(token != null) {
            CurdStatement right = binaryArithmetic();
            return new Comparison(left, token, right);
        }
        return left;
    }


    /**
     * ">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains"
     */
    private Token comparisonSymbol() {
        if (tokenSequence.curEqual(TokenType.LIKE, TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS,
                TokenType.LESS_EQUAL, TokenType.BANG_EQUAL, TokenType.EQUAL, TokenType.LIKE, TokenType.CONTAINS)) {
            Token token = tokenSequence.takeCur();
            tokenSequence.advance();
            return token;
        }
        return null;
    }


    /**
     * unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
     */
    protected CurdStatement binaryArithmetic() {
        CurdStatement expression = unaryArithmetic();
        while (tokenSequence.curEqual(TokenType.PLUS, TokenType.MINUS,
                TokenType.STAR, TokenType.SLASH)) {
            Token operator = tokenSequence.takeCur();
            tokenSequence.advance();
            CurdStatement right = unaryArithmetic();
            expression = new BinaryArithmetic(expression, operator, right);
        }
        return expression;
    }


    /**
     * ("!"|"-") unaryArithmetic | primary
     */
    protected CurdStatement unaryArithmetic() {
        // "!-123"是不符合语法规则的。但我们将这个问题推迟到运行时检测。
        if (tokenSequence.curEqual(TokenType.BANG, TokenType.MINUS)) {
            Token operator = tokenSequence.takeCur();
            tokenSequence.advance();
            CurdStatement curdStatement = unaryArithmetic();
            return new UnaryArithmetic(operator, curdStatement);
        }
        return primary();
    }


    /**
     * "true" | "false" | "null" | NUMBER
     */
    protected CurdStatement primary() {
        // "true" | "false" | "null" | NUMBER | STRING
        if (tokenSequence.equalThenAdvance(TokenType.FALSE)) {
            return new Literal(false);
        } else if (tokenSequence.equalThenAdvance(TokenType.TRUE)) {
            return new Literal(true);
        } else if (tokenSequence.equalThenAdvance(TokenType.NULL)) {
            return new Literal(null);
        } else if (tokenSequence.equalThenAdvance(TokenType.NUMBER, TokenType.STRING)) {
            return new Literal(tokenSequence.takePre().getLiteral());
        }
        return null;
    }


    /**
     * functionName "(" binaryArithmetic? ("," binaryArithmetic)* ")"
     * "date_to_timestamp" | "date_to_str" | "str_to_date" | "str_to_timestamp" | "timestamp_to_date" | "timestamp_to_str" | "now"
     */
    protected CurdStatement function() {
        Token method = tokenSequence.takeCur();
        tokenSequence.advance();
        tokenSequence.consume(TokenType.LEFT_PAREN, "函数参数需要以 '(' 开始");

        // 没有参数的情况
        if(tokenSequence.equalThenAdvance(TokenType.RIGHT_PAREN)) {
            return new Function(method);
        }

        // 有参数的情况
        ArrayList<CurdStatement> parameterList = new ArrayList<>();
        do {
            CurdStatement curdStatement;
            Token operator = tokenSequence.takeCur();
            if(checkFunctionName(operator)) {
                curdStatement = function();
            } else {
                curdStatement = binaryArithmetic();
            }
            assertFunctionParameter(curdStatement);
            parameterList.add(curdStatement);
        } while(tokenSequence.equalThenAdvance(TokenType.COMMA));
        tokenSequence.consume(TokenType.RIGHT_PAREN, "函数参数需要以 ')' 结尾");
        return new Function(method, parameterList);
    }


    /**
     * (IDENTIFIER "=" binaryArithmetic) ("," IDENTIFIER "=" binaryArithmetic)*
     */
    protected CurdStatement entryList() {
        List<Entry> entryList = new ArrayList<>();
        do {
            tokenSequence.consume(TokenType.IDENTIFIER, " 缺少列名 ");
            Token token = tokenSequence.takePre();
            tokenSequence.consume(TokenType.EQUAL, "缺少 '=' 关键字");
            CurdStatement curdStatement = binaryArithmetic();
            AssignmentList.Entry entry = new AssignmentList.Entry(token, curdStatement);
            entryList.add(entry);
        } while (tokenSequence.equalThenAdvance(TokenType.COMMA));
        return new AssignmentList(entryList);
    }


    private void assertFunctionParameter(CurdStatement curdStatement) {
        if (curdStatement instanceof BinaryArithmetic) {
            BinaryArithmetic binaryArithmetic = (BinaryArithmetic) curdStatement;
            assertFunctionParameter(binaryArithmetic.getLeftSelectStatement());
            assertFunctionParameter(binaryArithmetic.getRightSelectStatement());
        } else if(curdStatement instanceof UnaryArithmetic) {
            UnaryArithmetic unaryArithmetic = (UnaryArithmetic) curdStatement;
            assertFunctionParameter(unaryArithmetic.getSelectStatement());
        } else if(curdStatement instanceof GroupFunction
                || curdStatement instanceof SingleSelect || curdStatement instanceof Grouping) {
            throw new ParseException("函数的参数不能是 select语句、分组函数，不能被 '()' 包裹", tokenSequence);
        }
    }


    protected static Set<TokenType> functionNameSet = new HashSet<>();

    static {
        functionNameSet.add(TokenType.STR_TO_DATE);
        functionNameSet.add(TokenType.STR_TO_TIMESTAMP);
        functionNameSet.add(TokenType.DATE_TO_TIMESTAMP);
        functionNameSet.add(TokenType.DATE_TO_STR);
        functionNameSet.add(TokenType.TIMESTAMP_TO_DATE);
        functionNameSet.add(TokenType.TIMESTAMP_TO_STR);
        functionNameSet.add(TokenType.NOW);
    }

    protected boolean checkFunctionName(Token token) {
        return functionNameSet.contains(token.getType());
    }


}
